/*******************************************************************************
 * Copyright (c) 2009 Kevin Bierhoff and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kevin Bierhoff - initial API and implementation
 *******************************************************************************/
package com.googlecode.twelfdevtools.builder;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.text.IDocument;

import com.googlecode.twelfdevtools.server.TwelfServerEvent;
import com.googlecode.twelfdevtools.server.TwelfServerListener;


public class TwelfTypingListener implements TwelfServerListener {
	
	private final IProject project;
	
	private String curFile;
	private List<Map.Entry<String, String>> consts;

	public TwelfTypingListener(IProject project) {
		this.project = project;
	}
	
	public void notifyTwelfServerEvent(TwelfServerEvent e) {
		switch(e.getEventType()) {
		case OPEN:
			updateTypes();
			curFile = e.getFileName();
			consts = new LinkedList<Map.Entry<String, String>>();
			break;
		case CLOSE:
			updateTypes();
			curFile = null;
			consts = null;
			break;
		case CONSTANT:
			addConstFromTwelfString(e.getRawInfo());
			break;
		}
	}

	private void addConstFromTwelfString(String rawInfo) {
		final String decl;
		int dot = rawInfo.lastIndexOf('.');
		if(dot >= 0) {
			// snip away dot in the end
			decl = rawInfo.substring(0, dot).trim();
		}
		else {
			decl = rawInfo;
		}
		final String[] pieces = decl.split("\\s*:\\s*", 2);
		if(pieces.length != 2)
			return;
		Map.Entry<String, String> e = new Map.Entry<String, String>() {

			public String getKey() {
				return pieces[0];
			}

			public String getValue() {
				return pieces[1];
			}

			public String setValue(String value) {
				throw new UnsupportedOperationException("Cannot set new value for constant: " + decl);
			}
			
			@Override
			public String toString() {
				return decl;
			}
			
		};
		consts.add(e);
	}

	private void updateTypes() {
		if(curFile == null)
			return;
		assert consts != null;
		
		IFile f = project.getFile(curFile);
		IPath path = f.getFullPath();
		if (path == null)
			return;

		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
		try {
			manager.connect(path, LocationKind.IFILE, null);
		} 
		catch (CoreException ex) {
			ex.printStackTrace();
			return;
		}

		try {
			ITextFileBuffer buffer = manager.getTextFileBuffer(path, LocationKind.IFILE);
			IDocument doc = buffer.getDocument();
			String code = doc.get();
//			IAnnotationModel model= buffer.getAnnotationModel();
			
			// remove existing annotations
//			Iterator it = model.getAnnotationIterator();
//			while(it.hasNext()) {
//				Annotation a = (Annotation) it.next();
//				if(a.getType().equals(TwelfBuilder.TYPING_MARKER_TYPE))
//					model.removeAnnotation(a);
//			}
			
			markConstants(f, code);
			
			// annotate all occurrences of all consts
			// TODO this is really really inefficient
			// TODO this doesn't work right for "shadowed" constants
			// TODO this doesn't annotate constants defined in other files
			// TODO place markers only on constant definitions; infer uses in editor
//			for (Map.Entry<String, String> c : consts) {
//				// find first occurrence
//				int pos = code.indexOf(c.getKey());
//				while(pos >= 0) {
//					int end = pos + c.getKey().length();
//					try {
//						IMarker m = f.createMarker(TwelfBuilder.TYPING_MARKER_TYPE);
//						m.setAttribute(IMarker.MESSAGE, c.toString());
//						m.setAttribute(IMarker.CHAR_START, pos);
//						m.setAttribute(IMarker.CHAR_END, end);
//					} 
//					catch (CoreException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
					
//					Annotation a = new Annotation(true);
//					a.setText(c.toString());
//					model.addAnnotation(a, new Position(pos, c.getKey().length()));
					
					// find next occurrence
//					pos = code.indexOf(c.getKey(), end);
//				}
//			}
		} 
		finally {
			try {		
				manager.disconnect(path, LocationKind.IFILE, null);
			} 
			catch (CoreException ex) {
				ex.printStackTrace();
			}
		}
	}

	private void markConstants(IFile f, String code) {
		int pos = 0;
		for(Map.Entry<String, String> c : consts) {
			if(pos >= code.length())
				// weird: end of file reached, but still looking for constants
				// stop looking, since pointless...
				break;
			
			String id = c.getKey();
			// look for "<constname> :"
			Pattern constPattern = Pattern.compile("(" + Pattern.quote(id) + ")\\s*:", 
					Pattern.MULTILINE);
			Matcher m = constPattern.matcher(code);
			// start looking at current position in the file
			if(m.find(pos)) {
				// begin and end of const identifier in declaration
				int start = m.start(1);
				int endConst = m.end(1);
				// find end of constant declaration
				int end = code.indexOf('.', endConst);
				if (end < 0)
					// weird: couldn't find the . at the end of this decl
					// use the : as the end
					end = m.end() - 1;
				// put marker in
				markConstant(f, id, c.toString(), start, endConst-1, end);
				// continue search after end of this declaration
				pos = end + 1;
			}
			else {
				// weird: constant couldn't be found
				// just keep going looking for the next one
			}
		}
		
	}

	private void markConstant(IFile f, String id, String term, int start,
			int constEnd, int declEnd) {
		try {
			IMarker m = f.createMarker(TwelfBuilder.TYPING_MARKER_TYPE);
			m.setAttribute(IMarker.MESSAGE, term);
			m.setAttribute(IMarker.CHAR_START, start);
			m.setAttribute(IMarker.CHAR_END, constEnd);
			// custom attributes
			m.setAttribute(TwelfBuilder.ID_ATTRIBUTE, id);
			m.setAttribute(TwelfBuilder.CHAR_DECL_END, declEnd);
		} 
		catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
